#ifndef __MSOCKET_H__
#define __MSOCKET_H__

#if defined(_WIN32) || defined(_WIN64)
#include <Winsock2.h>
#include <ws2ipdef.h>

typedef SOCKET socket_t;
typedef int socklen_t;
typedef WSABUF socket_bufvec_t;

#define socket_invalid INVALID_SOCKET
#define socket_error SOCKET_ERROR

#if defined(_MSC_VER)
#pragma comment(lib, "Ws2_32.lib")
#pragma warning(disable : 4127)
#endif

#else
#include <sys/select.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <poll.h>

typedef int socket_t;
typedef struct iovec socket_bufvec_t;

#define socket_invalid -1
#define socket_error -1

#endif

#include <assert.h>
#include <stdio.h>

#ifndef IN
#define IN
#endif

#ifndef OUT
#define OUT
#endif

#ifndef INOUT
#define INOUT
#endif

#ifndef MSOCKET_API
#define MSOCKET_API 
#endif

extern MSOCKET_API int socket_init(void);
extern MSOCKET_API int socket_cleanup(void);
extern MSOCKET_API int socket_geterror(void);

extern MSOCKET_API socket_t socket_tcp(void);
extern MSOCKET_API socket_t socket_udp(void);
extern MSOCKET_API socket_t socket_raw(void);
extern MSOCKET_API socket_t socket_rdm(void);
extern MSOCKET_API int socket_close(socket_t sock);

extern MSOCKET_API int socket_connect(IN socket_t sock, IN const struct sockaddr *addr, IN socklen_t addrlen);
extern MSOCKET_API int socket_connect_ipv4(IN socket_t sock, IN const char *ip_or_dns, IN unsigned short port);
extern MSOCKET_API int socket_connect_ipv4_by_time(IN socket_t sock, IN const char *ip_or_dns, IN unsigned short port, IN int timeout);

// MSDN: When using bind with the SO_EXCLUSIVEADDR or SO_REUSEADDR socket option,
//       the socket option must be set prior to executing bind to have any affect
extern MSOCKET_API int socket_bind(IN socket_t sock, IN const struct sockaddr *addr, IN socklen_t addrlen);
extern MSOCKET_API int socket_bind_any(IN socket_t sock, IN unsigned short port);

extern MSOCKET_API int socket_listen(IN socket_t sock, IN int backlog);
extern MSOCKET_API socket_t socket_accept(IN socket_t sock, OUT struct sockaddr *addr, INOUT socklen_t *addrlen);

// socket read/write
extern MSOCKET_API int socket_send(IN socket_t sock, IN const void *buf, IN size_t len, IN int flags);
extern MSOCKET_API int socket_recv(IN socket_t sock, OUT void *buf, IN size_t len, IN int flags);
extern MSOCKET_API int socket_sendto(IN socket_t sock, IN const void *buf, IN size_t len, IN int flags, IN const struct sockaddr *to, IN socklen_t tolen);
extern MSOCKET_API int socket_recvfrom(IN socket_t sock, OUT void *buf, IN size_t len, IN int flags, OUT struct sockaddr *from, OUT socklen_t *fromlen);

/// @return <0-timeout/error, >0-read bytes
extern MSOCKET_API int socket_send_by_time(IN socket_t sock, IN const void *buf, IN size_t len, IN int flags, IN int timeout); // timeout: ms, -1==infinite
/// @return <0-timeout/error, >0-read bytes
extern MSOCKET_API int socket_send_all_by_time(IN socket_t sock, IN const void *buf, IN size_t len, IN int flags, IN int timeout); // timeout: ms, -1==infinite
/// @return 0-connection closed, <0-timeout/error, >0-read bytes
extern MSOCKET_API int socket_recv_by_time(IN socket_t sock, OUT void *buf, IN size_t len, IN int flags, IN int timeout); // timeout: ms, -1==infinite
/// @return 0-connection closed, <0-timeout/error, >0-read bytes(always is len)
extern MSOCKET_API int socket_recv_all_by_time(IN socket_t sock, OUT void *buf, IN size_t len, IN int flags, IN int timeout); // timeout: ms, -1==infinite

extern MSOCKET_API int socket_send_v(IN socket_t sock, IN const socket_bufvec_t *vec, IN size_t n, IN int flags);
extern MSOCKET_API int socket_recv_v(IN socket_t sock, IN socket_bufvec_t *vec, IN size_t n, IN int flags);
extern MSOCKET_API int socket_sendto_v(IN socket_t sock, IN const socket_bufvec_t *vec, IN size_t n, IN int flags, IN const struct sockaddr *to, IN socklen_t tolen);
extern MSOCKET_API int socket_recvfrom_v(IN socket_t sock, IN socket_bufvec_t *vec, IN size_t n, IN int flags, IN struct sockaddr *from, IN socklen_t *fromlen);

// Linux: select may update the timeout argument to indicate how much time was left
extern MSOCKET_API int socket_select(IN int n, IN fd_set *rfds, IN fd_set *wfds, IN fd_set *efds, IN struct timeval *timeout);
extern MSOCKET_API int socket_select_readfds(IN int n, IN fd_set *fds, IN struct timeval *timeout);
extern MSOCKET_API int socket_select_writefds(IN int n, IN fd_set *fds, IN struct timeval *timeout);
extern MSOCKET_API int socket_select_read(IN socket_t sock, IN int timeout);  // 1-read able, 0-timeout, <0-forever
extern MSOCKET_API int socket_select_write(IN socket_t sock, IN int timeout); // 1-write able, 0-timeout, <0-forever
extern MSOCKET_API int socket_readable(IN socket_t sock);                     // 1-read able, 0-can't, <0-error
extern MSOCKET_API int socket_writeable(IN socket_t sock);                    // 1-write able, 0-can't, <0-error

// socket options
extern MSOCKET_API int socket_setkeepalive(IN socket_t sock, IN int enable); // keep alive
extern MSOCKET_API int socket_getkeepalive(IN socket_t sock, OUT int *enable);
extern MSOCKET_API int socket_setlinger(IN socket_t sock, IN int onoff, IN int seconds); // linger
extern MSOCKET_API int socket_getlinger(IN socket_t sock, OUT int *onoff, OUT int *seconds);
extern MSOCKET_API int socket_setsendbuf(IN socket_t sock, IN size_t size); // send buf
extern MSOCKET_API int socket_getsendbuf(IN socket_t sock, OUT size_t *size);
extern MSOCKET_API int socket_setrecvbuf(IN socket_t sock, IN size_t size); // recv buf
extern MSOCKET_API int socket_getrecvbuf(IN socket_t sock, OUT size_t *size);
extern MSOCKET_API int socket_setsendtimeout(IN socket_t sock, IN size_t seconds); // send timeout
extern MSOCKET_API int socket_getsendtimeout(IN socket_t sock, OUT size_t *seconds);
extern MSOCKET_API int socket_setrecvtimeout(IN socket_t sock, IN size_t *seconds); // recv timeout
extern MSOCKET_API int socket_getrecvtimeout(IN socket_t sock, OUT size_t *seconds);
extern MSOCKET_API int socket_setreuseaddr(IN socket_t sock, IN int enable); // reuse addr.
extern MSOCKET_API int socket_getreuseaddr(IN socket_t sock, OUT int *enable);

// socket status
extern MSOCKET_API int socket_setnonblock(IN socket_t sock, IN int noblock); // non-block io, 0-block, 1-nonblock
extern MSOCKET_API int socket_setnondelay(IN socket_t sock, IN int nodelay); // non-delay io(Nagle Algorithm), 0-delay, 1-nodelay
extern MSOCKET_API int socket_getunread(IN socket_t sock, OUT size_t *size); // MSDN: Use to determine the amount of data pending in the network's input buffer that can be read from socket s

extern MSOCKET_API int socket_getname(IN socket_t sock, OUT char *ip, OUT unsigned short *port);
extern MSOCKET_API int socket_getpeername(IN socket_t sock, OUT char *ip, OUT unsigned short *port);

// socket utility
extern MSOCKET_API int socket_isip(IN const char *ip); // socket_isip("192.168.1.2") -> 0, socket_isip("www.google.com") -> -1
extern MSOCKET_API int socket_ip(IN const char *ip_or_dns, OUT char *ip);
extern MSOCKET_API int socket_addr_ipv4(OUT struct sockaddr_in *addrin, IN const char *ip_or_dns, IN unsigned short port);

extern MSOCKET_API void socket_setbufvec(INOUT socket_bufvec_t *vec, IN int idx, IN void *ptr, IN size_t len);

// 以下函数只有win才有
// multicast
/*
extern MSOCKET_API int socket_multicast_join(IN socket_t sock, IN const char *group, IN const char *source, IN const char *local);
extern MSOCKET_API int socket_multicast_leave(IN socket_t sock, IN const char *group, IN const char *source, IN const char *local);
extern MSOCKET_API int socket_multicast_join_source(IN socket_t sock, IN const char *group, IN const char *source, IN const char *local);
extern MSOCKET_API int socket_multicast_leave_source(IN socket_t sock, IN const char *group, IN const char *source, IN const char *local);
*/

#if defined(_MSC_VER)
#pragma warning(default : 4127)
#endif

#endif